﻿using System;
using System.Collections.Generic;
using System.Threading;
using Com.PhilChuang.Utils;

namespace Com.PhilChuang.Pmvp.Ui.Presenters
{
	public abstract class AbstractPresenter
	{
		#region Static variables
		private static readonly Type s_AbstractPresenterType = typeof (AbstractPresenter);
		private static Type s_ReturnToPresenter;
		/// <summary>
		/// When set, Presenters will check this value when the View is loaded and and close the Views until the specified Presenter is reached.
		/// </summary>
		protected static Type ReturnToPresenter
		{
			get { return s_ReturnToPresenter; }
			set
			{
				if (value != null && !s_AbstractPresenterType.IsAssignableFrom (value))
					throw new ArgumentException ("{0} is not assignable to {1}".FormatWith (value.Name, s_AbstractPresenterType.Name));
				s_ReturnToPresenter = value;
			}
		}
		#endregion

		#region Instance Variables
		protected List<IDisposable> components = new List<IDisposable> ();
		#endregion

		#region Constructors / Dispose / Init
		protected AbstractPresenter ()
		{
			DebugWriteLine ("new");
		}

		private bool m_IsDisposing = false;

		/// <summary>
		/// Disposes of held resources.
		/// Call base impl as the last step.
		/// </summary>
		public void Dispose ()
		{
			DebugWriteLine ("Dispose");
			m_IsDisposing = true;

			OnDispose ();
	
			foreach (var disp in components)
				disp.Dispose ();
		}

		/// <summary>
		/// Disposes of held resources.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void OnDispose ()
		{
		}
		#endregion

		#region Private Functionality
		private readonly Object lock_m_NumAsyncThreads = new object ();
		private int m_NumAsyncThreads = 0;
		protected readonly ManualResetEvent m_WaitForAsyncThreadsToFinishMre = new ManualResetEvent (true);

		/// <summary>
		/// Executes asynchronous tasks that access the View.   Using this method will ensure that Dispose does execute until all async tasks have finished.
		/// </summary>
		/// <param name="a"></param>
		protected void ExecuteAsync (Action a)
		{
			ThreadPool.QueueUserWorkItem (
				obj =>
					{
						if (m_IsDisposing) return;
						lock (lock_m_NumAsyncThreads)
						{
							if (m_NumAsyncThreads == 0)
								m_WaitForAsyncThreadsToFinishMre.Reset ();
							Interlocked.Increment (ref m_NumAsyncThreads);
						}
						try
						{
							if (!m_IsDisposing) a ();
						}
						finally
						{
							lock (lock_m_NumAsyncThreads)
							{
								Interlocked.Decrement (ref m_NumAsyncThreads);
								if (m_NumAsyncThreads == 0)
									m_WaitForAsyncThreadsToFinishMre.Set ();
							}
						}
					});
		}

		protected void DebugWriteLine (String msg)
		{
#if DEBUG
			System.Diagnostics.Debug.WriteLine (String.Format ("[{0}]\t{1}\t{2}", System.Threading.Thread.CurrentThread.ManagedThreadId, GetType ().Name, msg));
#endif
		}
		#endregion
	}
}